package com.googlecode.recycled.xml;

import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.googlecode.recycled.lang.assertions.Assert;
import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import java.util.Map;

public class XsdParser {

    protected XPaths xPaths;

    protected Element root;

    protected Map<String, Element> types = Maps.newHashMap();

    public XsdParser(Node... xsds) {
        Assert.notEmpty(xsds);

        xPaths = new XPathsBuilder().namespaceContext(new XsdSchemaNamespaceContext()).build(xsds[0]);
        root = Assert.notNull(xPaths.element("/schema/element"), "Empty root.");
        resolveTypes(root().getOwnerDocument().getDocumentElement());

        for (int i = 1; i < xsds.length; i++) {
            resolveTypes(xsds[i].getOwnerDocument().getDocumentElement());
        }
    }

    protected void resolveTypes(Element xsdRoot) {
        for (Element type : new XPaths(xsdRoot).elements("simpleType | complexType")) {
            types.put(type.getAttribute("name"), type);
        }
    }

    public Element root() {
        return root;
    }

    public Iterable<Element> doSearchElement(Element xsdElement, String path, String fallbackPath) {
        Iterable<Element> elements = new XPaths(xsdElement).elements(path);
        if (Iterables.isEmpty(elements)) {
            String typeName = xsdElement.getAttribute("type");
            if (StringUtils.isNotBlank(typeName)) {
                Element type = Assert.notNull(types.get(typeName), "Type not loaded: " + typeName);
                return new XPaths(type).elements(fallbackPath);
            }
        }
        return elements;
    }

    public Iterable<Element> elements(Element xsdElement) {
        return doSearchElement(xsdElement, "complexType/sequence/element", "sequence/element");
    }

    public Iterable<Element> attributes(Element xsdElement) {
        return doSearchElement(xsdElement, "complexType/attribute", "attribute");
    }

    public Iterable<Element> restrictions(Element xsdType) {
        return new XPaths(xsdType).elements("restriction/*");
    }

}
